package shttps

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"
	"time"
)

//封住https请求

type Http struct {
	client     *http.Client           //请求客户端
	reuse      bool                   //重用客户端,未实现
	url        string                 //请求连接
	heads      map[string]string      //请求头
	querys     map[string]string      //query
	req        interface{}            //请求参数,body
	res        interface{}            //响应参数
	Json       map[string]interface{} //json里面读取对应的参数,未实现
	spend      string                 //请求时间消耗
	debug      bool                   //是否打印日志
}

//请求模式
func New(url string) Http {
	data := Http{
		client: NewClient(),
	}
	return data.New(url)
}

//重置参数,heads,querys,请求参数,响应参数
func (this Http) New(url string) Http {
	this.url = url
	this.heads = map[string]string{
		"Content-Type": "application/json;charset=utf-8",
		"Accept":       "application/json",
		"Connection":   "close",
	}
	this.querys = make(map[string]string)
	this.req = nil
	this.res = nil
	return this
}

//添加请求头
func (this Http) AddHead(key, value string) Http {
	this.heads[key] = value
	return this
}

//请求数据
func (this Http) Req(v interface{}) Http {
	this.req = v
	return this
}

//响应数据
func (this Http) Res(v interface{}) Http {
	this.res = v
	return this
}

func (this Http) Get() error {
	return this.Do(http.MethodGet)
}

func (this Http) Post() error {
	return this.Do(http.MethodPost)
}

func (this Http) Do(method string) error {
	res := this.do(method)
	if res.Err() != nil {
		return res.err
	}
	if res.Code() < 200 || res.Code() >= 300 {
		return errors.New(fmt.Sprintf(`%s(%d):%s`, string([]byte{231, 138, 182, 230, 128, 129, 231, 160, 129}), res.Code(), string(res.Body())))
	}
	return nil
}

func (this Http) do(method string) Response {
	//========= 请求数据处理 =========

	t := time.Now()
	reqBytes, err := this.reqBytes()
	if err != nil {
		return newResponseErr(err)
	}
	request, err := http.NewRequest(method, dealQuery(this.url, this.querys), bytes.NewBuffer(reqBytes))
	if err != nil {
		return newResponseErr(err)
	}
	defer request.Body.Close()
	for i, v := range this.heads {
		//这样可以设置小写的请求头
		request.Header[i] = []string{v}
	}
	var res = new(http.Response)

	//========= 请求方式 =========

	if strings.ToUpper(method) == "GET" {
		res, err = http.Get(request.URL.String())
		if err != nil {
			return newResponseErr(err)
		}
	} else {
		if this.client == nil {
			this.client = NewClient()
		}
		res, err = this.client.Do(request)
		if err != nil {
			return newResponseErr(err)
		}
	}
	defer res.Body.Close()

	//========= 响应数据处理 =========

	data := newResponse(res)
	this.spend = "耗时:" + time.Now().Sub(t).String()
	this.print(request, string(reqBytes), data)
	if err := this.responseBind(data.Body()); err != nil {
		return newResponseErr(err)
	}
	return data
}

//特殊处理byte数组和字符串
func (this Http) reqBytes() ([]byte, error) {
	var bs []byte
	var err error
	switch val := this.req.(type) {
	case string:
		bs = []byte(val)
	case *string:
		bs = []byte(*val)
	case []byte:
		bs = val
	case *[]byte:
		bs = *val
	default:
		bs, err = json.Marshal(this.req)
	}
	return bs, err
}

//拼接query参数的连接
func dealQuery(url string, m map[string]string) string {
	if len(m) != 0 {
		n := strings.Index(url, "?")
		if n == -1 {
			url += "?"
			for i, v := range m {
				url += i + "=" + v + "&"
			}
		} else {
			for i, v := range m {
				url += "&" + i + "=" + v
			}
		}
	}
	return url
}

//默认详细日志
func (this Http) print(req *http.Request, body string, res Response) {
	if this.debug {
		fmt.Println("----------------------------------------")
		fmt.Println(req.Method, "\t", req.URL.String())
		if body != "null" {
			fmt.Println(body)
		}
		fmt.Println("----------------------------------------")
		fmt.Println(res.Response.Proto, res.Response.Status)
		for _, v := range res.Headers() {
			fmt.Printf(v)
		}
		fmt.Println()
		fmt.Println(res.BodyString())
		fmt.Println("----------------------------------------")
	}
}


//数据解析
func (this Http) responseBind(body []byte) error {
	if this.res != nil && len(body) != 0 {
		switch val := this.res.(type) {
		case *string:
			*val = string(body)
		case *[]byte:
			*val = body
		default:
			if err := json.Unmarshal(body, this.res); err != nil {
				return err
			}
		}
	}
	return nil
}
